#include "textbox.h"

TextBox::TextBox(std::string id, int x, int y, int size_x, int size_y, ALLEGRO_FONT* text_font,
		int padding, int indention, std::string background_graphic, int scrollbar_width, int scrollbar_button_height,
		int scrollbar_slider_padding, std::string scrollbar_slider_background_graphic, int scrollbar_slider_height,
		std::string scrollbar_slider_graphic_up, std::string scrollbar_slider_graphic_down,
		std::string scrollbar_button_graphic_top_up, std::string scrollbar_button_graphic_top_down,
		std::string scrollbar_button_graphic_bottom_up, std::string scrollbar_button_graphic_bottom_down)
	: GuiElement(id, x, y, size_x, size_y)
{
	m_text_font = text_font;
	m_padding = padding;
	m_indention = indention;
	m_scrollbar_width = scrollbar_width;
	m_background_graphic = background_graphic;

	// Create the scrollbar.
	m_scrollbar = new ScrollBar(m_id + "_scrollbar", m_x + m_size_x - m_padding - m_scrollbar_width, m_y + m_padding,
		m_scrollbar_width, m_size_y - 2 * m_padding);

	m_scrollbar->ScrollToBottom();
}

TextBox::~TextBox()
{
	delete m_scrollbar;
	al_destroy_font(m_text_font);
}

void TextBox::AddLine(std::string text, const unsigned char color[3])
{
	TextBoxLine line;
	line.color = al_map_rgb(color[0], color[1], color[2]);
	line.words = SplitStringByDelimiter(text, ' ');

	// Add to the list.
	m_line_list.push_back(line);

	// We always want to scroll back to the bottom when a new message has been posted.
	m_scrollbar->ScrollToBottom();
}


void TextBox::PrimaryMouseButtonUp(int x, int y)
{
	m_scrollbar->PrimaryMouseButtonUp(x, y);
}

void TextBox::PrimaryMouseButtonDown(int x, int y)
{
	m_scrollbar->PrimaryMouseButtonDown(x, y);
}

void TextBox::MouseAxisChange(int x, int y)
{
	m_scrollbar->MouseAxisChange(x, y);
}

void TextBox::Update()
{
	// Recalculate every time, there might be new lines.
	m_rows_to_draw.clear();

	// We need the box width in order to know whether a line is too long or not.
	const int box_width = m_size_x - m_scrollbar_width - 2 * m_padding;

	// TODO: This costs us a lot of performance. Maybe we can try to not calculated lines that have no possibility
	// of being shown at the current scrollbar value anyway. This is not easy though. We need to take a look at
	// the scrollbar value and the possible number of entries for that.
	int line_start = 0;

	// Go through the lines to compute.
	for (unsigned i = line_start; i < m_line_list.size(); i++)
	{
		// This keeps track of the current x position and the words we have put into this row so far. Also we want
		// to memorize whether this is the first row of a line so we can indent all the others (for better looks).
		int cur_x = 0;
		std::string current_row = "";
		bool first_row_for_line = true;

		// For every word that belongs to the current line...
		for (std::list<std::string>::iterator word_it = m_line_list.at(i).words.begin();
			word_it != m_line_list.at(i).words.end(); word_it++)
		{
			// See whether it fits into the box. If yes, just append it...
			int next_word_width = al_get_text_width(m_text_font, word_it->c_str());
			if (cur_x + next_word_width <= box_width)
			{
				current_row += *word_it;
			}
			// ...if not, the current row is complete. Save it and start a new row, with the current word as initial
			// value. Also, since we are not in the first row of the line anymore, we have to consider indention.
			else
			{
				TextBoxRow row;
				row.color = m_line_list.at(i).color;
				row.text = current_row;
				row.indent = !first_row_for_line;
				m_rows_to_draw.push_back(row);

				current_row = *word_it;
				first_row_for_line = false;
				cur_x = m_indention;
			}

			// If this is the last word of the line, we want to save the row too.
			if (word_it == --(m_line_list.at(i).words.end()) && current_row != " ")
			{
				TextBoxRow row;
				row.color = m_line_list.at(i).color;
				row.text = current_row;
				row.indent = !first_row_for_line;
				m_rows_to_draw.push_back(row);
			}

			// Space between every word. We also need to calculate the space needed for that.
			current_row += " ";
			cur_x += next_word_width + al_get_text_width(m_text_font, " ");
		}
	}

	// Now we calculate the step size for the scrollbar. We need to know how many entries can fit. If there are
	// less rows than can fit in, everything's ok and we don't need a step size. If there are more, we have to
	// set step size accordingly.
	// TODO: This is not 100% right yet, but almost. Sometimes, a line is jumped.
	unsigned maximum_row_space = (m_size_y - 2 * m_padding) / al_get_font_line_height(m_text_font);
	if (m_rows_to_draw.size() <= maximum_row_space)
		m_scrollbar->set_step(0.0f);
	else
		m_scrollbar->set_step(1.0f / (m_rows_to_draw.size() - maximum_row_space));

	m_scrollbar->Update();
}

void TextBox::Draw()
{
	// Draw the background.
	ALLEGRO_BITMAP* graphic = allegro_bitmap_resource_manager.GetResource(m_background_graphic);
	al_draw_scaled_bitmap(graphic, 0, 0, al_get_bitmap_width(graphic), al_get_bitmap_height(graphic),
		m_x, m_y, m_size_x, m_size_y, NULL);
	
	// Prepare the bitmap for the text. This is only as big as the box can hold text.
	ALLEGRO_BITMAP *text_bitmap = al_create_bitmap(m_size_x - 2 * m_padding - m_scrollbar_width, m_size_y - 2 * m_padding);
	al_set_target_bitmap(text_bitmap);

	// Determine, on which row we begin to draw. If there are less rows than can fit in, we start with the last entry
	// (we want to show the latest lines at the bottom). If there are more, the scrollbar value decides which we show.
	unsigned maximum_row_space = (m_size_y - 2 * m_padding) / al_get_font_line_height(m_text_font);
	unsigned start_row = (m_rows_to_draw.size() > maximum_row_space)
		? m_scrollbar->value() * (m_rows_to_draw.size() - maximum_row_space) + maximum_row_space - 1
		: m_rows_to_draw.size() - 1;
	
	// We store the current y. This lets us decide when we can stop drawing. If cur_y plus the height of the font is
	// below zero, the row won't be visible anymore and we can stop drawing. Also, we stop drawing when i < 0, i.e.
	// when we have drawn every row.
	int cur_y = al_get_bitmap_height(text_bitmap) - al_get_font_line_height(m_text_font);
	for (unsigned i = start_row; i >= 0 && i < m_rows_to_draw.size() && cur_y + al_get_font_line_height(m_text_font) >= 0; i--)
	{
		// If it is not the first row of the line, we indent to make it look better.
		int x_pos = 0;
		if (m_rows_to_draw.at(i).indent)
			x_pos += m_indention;

		// Finally draw the row.
		al_draw_text(m_text_font, m_rows_to_draw.at(i).color, x_pos, cur_y, NULL,
			m_rows_to_draw.at(i).text.c_str());

		// Change the position of y to jump one line higher.
		cur_y -= al_get_font_line_height(m_text_font);
	}
	
	// Set target bitmap back to the backbuffer, copy the text bitmap to the right position and then get rid of
	// it, it is not longer needed but created on the next draw once again (the text to show could have changed).
	al_set_target_bitmap(al_get_backbuffer(System::GetInstance()->display()));
	al_draw_bitmap(text_bitmap, m_x + m_padding, m_y + m_padding, NULL);
	al_destroy_bitmap(text_bitmap);
	
	m_scrollbar->Draw();
}